Uma forma esquemática, mas útil de encarar o processo de análise de dados e de produção de conhecimento a partir de informações secundárias é esta figura:

Ela acaba sendo um ponto de partida interessante porque contextualiza a maioria dos pacotes que vamos utilizar. O readr é uma maneira de facilitar e tornar mais rápida a importação de dados em formatos comuns, como o texto delimitado por separadores e o formato colunado com larguras-fixas utilizado pelo IBGE.
A tibble é uma proposta de modernização para o data.frame, aproveitando a estrutura flexível e poderosa, mas mudando certas convenções, como a conversão de strings para fatores, permitindo a criação de colunas de listas, utilização de nomes mais complexos para colunas, etc.
O tidyr é uma forma de reformatar (reshape) bancos de dados que vêm em formatos que dificultam seu processamento, permitindo que o analista facilmente reconfigure a informação para o formato mais adequado.
O mesmo pode ser dito para os outros pacotes que veremos nos próximos dias. Cada um deles foi pensado para resolver um problema ou facilitar uma rotina de trabalho relacionada com um dos passos descritos acima.
Vários dos conceitos que guiam o design do tidyverse são orientados por uma certa filosofia. Esta filosofia dita que a principal preocupação por trás de um programa não é que ele funcione. Eventualmente e com um pouco de persistência, é quase sempre possível chegar a uma solução técnica adequada. A principal questão é que o programa é uma ferramenta de comunicação com outras pessoas que o lerão, seja seus colegas, colaboradores, alunos ou você mesmo, daqui a 2 anos, quando lembrar que você já escreveu um programa para aquela tarefa. Trago aqui alguns trechos de Literate Programming, de Donald Knuth (1984), traduzidos livremente por este que vos fala:
Creio que chegou a hora de melhorar significativamente a documentação dos programas, e que podemos atingir este objetivo tratando programas como obras literárias. Por isso o título: “Programação Literária”. Abandonemos os velhos hábitos de construir programas: ao invés de imaginar que nossa tarefa principal é instruir um computador sobre o que deve ser feito, concentremo-nos em explicar aos seres humanos o que queremos que o computador faça. O praticante da programação literária pode ser visto como um ensaísta, cuja principal preocupação é com a clareza da exposição e a excelência no estilo. Tal autor, com um dicionário na mão, escolhe os nomes das variáveis com cuidado e explica claramente seu significado. Ele ou ela esmera por um programa que é compreensível porque seus conceitos foram introduzidos na melhor ordem possível para o entendimento humano, utilizando uma mistura de métodos formais e informais que se complementam.
Assim, várias das escolhas feitas na construção de tidyverse procuram reforçar essa característica de interpretabilidade dos programas, como a ordem lógica das operações com o pipe (%>%), funções com nomes mais longos e semânticos, a utilização de uma função específica para cada tarefa, ao invés da adaptação de funções genéricas para uma grande variedade de tarefas. O propósito de um código escrito como tidyverse é que, no limite, a própria síntaxe do código funcione como parte da documentação do programa.
%>%Usuários de longa data do R já podem ter encontrado no mato esse animal estranho e podem ter ficado confusos com seu significado. O pipe é de origem humilde e nasceu nos sistemas Unix há muitas décadas atrás. Seu objetivo é muito simples: e se você tiver uma sequência de computações em que cada uma recebe o resultado daquela feita anteriormente? Claro que você poderia fazer:
x <- 1:10
y <- diff(x)
mean(y)## [1] 1
Mas não seria interessante pular os objetos intermediários e ir direto ao ponto? O pipe vem do pacote magrittr, mas ele vêm carregado em quase todos os pacotes do tidyverse. Vamos carregar logo o tibble.
# Para ter acesso ao pipe, basta carregar um pacote do tidyverse, como tibble, dplyr, tidyr, etc.
library(tibble)
x %>% diff() %>% mean()## [1] 1
Vejamos um exemplo mais real, quantos artistas existem no dataset billboard? Podemos usar unique e length para descobrir.
x <- unique(tidyr::billboard$artist)
length(x)## [1] 228
Mas com %>% fica bem melhor:
tidyr::billboard %>% .$artist %>% unique() %>% length()## [1] 228
Ok, mas como ele funciona? É simples, o pipe carrega o objeto a sua esquerda num ponto . invisível que é automaticamente passado como o primeiro argumento da função à direita.
x <- 1:10
mean(x)## [1] 5.5
x %>% mean()## [1] 5.5
OK, mas e se meu argumento não for o primeiro, ainda posso usar pipe? Pode! É só usar explicitamente um ponto no lugar onde você quer aproveitar o efeito:
iris %>% boxplot(Sepal.Length ~ Species, data = .)
O ponto . depois de data indica indica que ali deve ser colocado o iris. O pipe é uma peça chave de muitas funções do tidyverse, não porque ele seja obrigatório, mas sim porque ele permite expressar sequências de operações numa ordem mais lógica, do tipo: “Primeiro faça a, então b, então c, …”, ao contrário da forma como isto é geralmente feito usando parênteses para precedência.
# Compare
mean(diff(1:10))## [1] 1
1:10 %>% diff %>% mean## [1] 1
E assim fica desmistificado o mistério do pipe! Um último pulo do gato: pelo amor de deus ninguém digita Shift + %, >, Shift + %, basta usar o atalho: Ctrl + Shift + M que ele põe um pipe separado por espaços %>%.
Usuários do R provavelmente vão estar familiarizados com os nossos leitores de arquivos mais comuns: read.table e read.csv. Talvez muitos de vocês já até memorizaram alguns dos argumentos mais comuns. Não é o caso aqui de revisitar esta função, mas o readr tem muitos paralelos com elas, porque é pensado como uma nova versão da mesma coisa.
# Comecemos carregando o readr
library(readr)O readr, como as funções de leitura do base é uma coleção de parsers, que transformam texto em objetos R com o tipo desejado.
parse_number(c("1", "20", "38"))## [1] 1 20 38
parse_character(c("banana", "maçã", "pêra"))## [1] "banana" "ma<e7><e3>" "p<ea>ra"
# Note os acentos e caracteres especiais
parse_character(c("banana", "maçã", "pêra"), locale = locale(encoding = "Windows-1252"))## [1] "banana" "maçã" "pêra"
parse_logical(c("true", "false", "true"))## [1] TRUE FALSE TRUE
Em geral, a gente não precisa descer tanto o nível, a gente vai trabalhar mesmo é com os leitores de dados “retangulares”. Como os do base, eles são read_csv, read_table, etc. Vamos trabalhar com bancos de dados que já vêm no pacote, para facilitar o processo.
# Lista os datasets que vem no pacote
readr_example()## [1] "challenge.csv" "epa78.txt" "example.log"
## [4] "fwf-sample.txt" "massey-rating.txt" "mtcars.csv"
## [7] "mtcars.csv.bz2" "mtcars.csv.zip"
Uma coisa que gosto de fazer é olhar como o arquivo está organizado antes de tentar abrí-lo. Muitos de vocês podem fazer isso com readLines. Ela ganhou sua versão no pacote com read_lines.
# Vamos tentar abrir massey-rating.txt
read_lines(readr_example("massey-rating.txt"), n_max = 10)## [1] "UCC PAY LAZ KPK RT COF BIH DII ENG ACU Rank Team Conf"
## [2] " 1 1 1 1 1 1 1 1 1 1 1 Ohio St B10 "
## [3] " 2 2 2 2 2 2 2 2 4 2 2 Oregon P12 "
## [4] " 3 4 3 4 3 4 3 4 2 3 3 Alabama SEC "
## [5] " 4 3 4 3 4 3 5 3 3 4 4 TCU B12 "
## [6] " 6 6 6 5 5 7 6 5 6 11 5 Michigan St B10 "
## [7] " 7 7 7 6 7 6 11 8 7 8 6 Georgia SEC "
## [8] " 5 5 5 7 6 8 4 6 5 5 7 Florida St ACC "
## [9] " 8 8 9 9 10 5 7 7 10 7 8 Baylor B12 "
## [10] " 9 11 8 13 11 11 12 9 14 9 9 Georgia Tech ACC "
# Identificando o separador, escolho a função adequada
read_table(readr_example("massey-rating.txt"))##
## -- Column specification --------------------------------------------------------
## cols(
## UCC = col_double(),
## PAY = col_double(),
## LAZ = col_double(),
## KPK = col_double(),
## RT = col_double(),
## COF = col_double(),
## BIH = col_double(),
## DII = col_double(),
## ENG = col_double(),
## ACU = col_double(),
## Rank = col_double(),
## Team = col_character(),
## Conf = col_character()
## )
Como identifiquei que as colunas estavam separadas por espaços, utilizei read_table cujo delimitador é o espaço " ".
A segunda feature mais interessante do readr, é uma interface para selecionar os tipos de colunas que serão importadas. Vejamos o seguinte exemplo.
# Vamos abrir mtcars.csv
read_lines(readr_example("mtcars.csv"), n_max = 10)## [1] "\"mpg\",\"cyl\",\"disp\",\"hp\",\"drat\",\"wt\",\"qsec\",\"vs\",\"am\",\"gear\",\"carb\""
## [2] "21,6,160,110,3.9,2.62,16.46,0,1,4,4"
## [3] "21,6,160,110,3.9,2.875,17.02,0,1,4,4"
## [4] "22.8,4,108,93,3.85,2.32,18.61,1,1,4,1"
## [5] "21.4,6,258,110,3.08,3.215,19.44,1,0,3,1"
## [6] "18.7,8,360,175,3.15,3.44,17.02,0,0,3,2"
## [7] "18.1,6,225,105,2.76,3.46,20.22,1,0,3,1"
## [8] "14.3,8,360,245,3.21,3.57,15.84,0,0,3,4"
## [9] "24.4,4,146.7,62,3.69,3.19,20,1,0,4,2"
## [10] "22.8,4,140.8,95,3.92,3.15,22.9,1,0,4,2"
# Identificamos o separador de colunas, selecionamos a função adequada
read_csv(readr_example("mtcars.csv"))##
## -- Column specification --------------------------------------------------------
## cols(
## mpg = col_double(),
## cyl = col_double(),
## disp = col_double(),
## hp = col_double(),
## drat = col_double(),
## wt = col_double(),
## qsec = col_double(),
## vs = col_double(),
## am = col_double(),
## gear = col_double(),
## carb = col_double()
## )
O console nos mostra que a leitura do banco foi completada, mas também mostra Column Specification. Isto indica qual o tipo de dado que foi identificado automaticamente numa análise feita pela função guess_parser. Em diversos casos, nós podemos querer identificar manualmente as colunas. Vejamos um exemplo:
# Vamos identificar as colunas com spec
spec_csv(readr_example("mtcars.csv"))##
## -- Column specification --------------------------------------------------------
## cols(
## mpg = col_double(),
## cyl = col_double(),
## disp = col_double(),
## hp = col_double(),
## drat = col_double(),
## wt = col_double(),
## qsec = col_double(),
## vs = col_double(),
## am = col_double(),
## gear = col_double(),
## carb = col_double()
## )
## cols(
## mpg = col_double(),
## cyl = col_double(),
## disp = col_double(),
## hp = col_double(),
## drat = col_double(),
## wt = col_double(),
## qsec = col_double(),
## vs = col_double(),
## am = col_double(),
## gear = col_double(),
## carb = col_double()
## )
# Copia e cola, modifica as colunas que queremos alterar
spec_cols <- cols(
mpg = col_double(),
cyl = col_factor(), # N de cilindros do automóvel
disp = col_double(),
hp = col_double(),
drat = col_double(),
wt = col_double(),
qsec = col_double(),
vs = col_double(),
am = col_factor(c("0", "1")), # Transmissão automática ou manual
gear = col_double(),
carb = col_double()
)
df <- read_csv(readr_example("mtcars.csv"), col_types = spec_cols)
df# Para importar apenas colunas selecionadas, utilize 'cols_only()'
spec_cols2 <- cols_only(
mpg = col_double(),
cyl = col_factor(), # N de cilindros do automóvel
am = col_factor(c("0", "1")), # Transmissão automática ou manual
gear = col_double(),
carb = col_double()
)
df2 <- read_csv(readr_example("mtcars.csv"), col_types = spec_cols2)
df2# Para indicar os tipos de colunas de um jeito mais sucinto, utilize uma string:
df <- read_csv(readr_example("mtcars.csv"), col_types = "dfddddddfdd")
# Só cuidado pra não perder a conta dos ds!
dfVocê também pode querer definir características de localização, como a codificação de caracteres, os separadores de decimal e de milhar e etc. A melhor forma de fazer isso é definir um locale.
meu_locale <- locale(encoding = "UTF-8", decimal_mark = ",", grouping_mark = ".")Aí é só passar isso pra uma das funções do pacote sob o argumento locale
read_csv(readr_example("mtcars.csv"), locale = meu_locale)##
## -- Column specification --------------------------------------------------------
## cols(
## mpg = col_number(),
## cyl = col_double(),
## disp = col_number(),
## hp = col_double(),
## drat = col_number(),
## wt = col_number(),
## qsec = col_number(),
## vs = col_double(),
## am = col_double(),
## gear = col_double(),
## carb = col_double()
## )
Existe ainda a possibilidade de ler dados colunados com largura-fixa. readr implementa quatro funções diferentes para ajudar na construção do dicionário:
# Nossos dados
x <- readr_example("fwf-sample.txt")
read_lines(x, n_max = 10)## [1] "John Smith WA 418-Y11-4111"
## [2] "Mary Hartford CA 319-Z19-4341"
## [3] "Evan Nolan IL 219-532-c301"
# separados por espaço
dic1 <- fwf_empty(x)
dic1## $begin
## [1] 0 5 20 30
##
## $end
## [1] 4 13 22 NA
##
## $skip
## [1] 0
##
## $col_names
## [1] "X1" "X2" "X3" "X4"
df <- read_fwf(file = x, col_positions = dic1)##
## -- Column specification --------------------------------------------------------
## cols(
## X1 = col_character(),
## X2 = col_character(),
## X3 = col_character(),
## X4 = col_character()
## )
df# indicando a largura da coluna
larguras <- c(20, 10, 12)
dic2 <- fwf_widths(larguras)
dic2df <- read_fwf(file = x, col_positions = dic2)##
## -- Column specification --------------------------------------------------------
## cols(
## X1 = col_character(),
## X2 = col_character(),
## X3 = col_character()
## )
df# indicando onde cada coluna começa e termina
comeca <- c(1, 21, 30)
termina <- c(20, 29, 42)
dic3 <- fwf_positions(comeca, termina)
dic3df <- read_fwf(file = x, col_positions = dic3)##
## -- Column specification --------------------------------------------------------
## cols(
## X1 = col_character(),
## X2 = col_character(),
## X3 = col_character()
## )
df# indicando pares nome-valor
dic4 <- fwf_cols(
nome = c(1, 20),
uf = c(21, 29),
numero = c(30, 42))
dic4df <- read_fwf(file = x, col_positions = dic4)##
## -- Column specification --------------------------------------------------------
## cols(
## nome = col_character(),
## uf = col_character(),
## numero = col_character()
## )
dfEspecificar dicionários para arquivos colunados é um pé-no-saco, por sorte, existem pacotes que já fizeram parte desse trabalho por nós. O readr não melhora muita o serviço manual de construção de dicionários, o que ele oferece é um ganho de performance tremendo. read_fwf é centenas de vezes mais rápido que o base read.fwf.
Em termos do que o pacote faz, é basicamente isso. A única coisa que falta mencionar é que ele importa os dados como tibbles ao invés do data.frame padrão, mas isso já é um ótimo gancho pra nossa próxima parte.
library(tibble)Tibbles são basicamente data.frames com um método mais bonitinho de print. Elas automaticamente se ajustam a largura da sua tela, omitindo as colunas que estouram, e por padrão imprimem só as 10 primeiras observações. Outras características que pessoalmente gosto, é que elas informam o tipo de variável junto com o nome, arrendondam digitos significativos, destacam números grandes, negativos, NAs e etc.
df <- read_csv(readr_example("mtcars.csv"), col_types = cols()) # omitir a especificação
dfDo ponto de vista prático, elas funcionam exatamente igual a data.frames, tudo que você pode fazer com um data.frame, você faz com tibbles. O que elas trazem de novidade é:
df1 <- data.frame(x = list(1:5, 1:10, 1:20))
df1df2 <- tibble(x = list(1:5, 1:10, 1:20))
df2names(data.frame(`nome hipster` = 1))## [1] "nome.hipster"
names(tibble(`nome hipster` = 1))## [1] "nome hipster"
# erro
data.frame(x = 1:5, y = x ^ 2)## Error in x^2: argumento não-numérico para operador binário
# funciona
tibble(x = 1:5, y = x ^ 2)data.frame(state.x77)as_tibble(state.x77)data.frame(x = 1:10, y = 1:5)# erro
tibble(x = 1:10, y = 1:5)## Error: Tibble columns must have compatible sizes.
## * Size 10: Existing data.
## * Size 5: Column `y`.
## i Only values of size one are recycled.
tibble(x = 1:10, y = 1)tibble(x = 1:10, y = c(1:5, 1:5))[df1 <- data.frame(x = 1:3, y = 3:1)
class(df1[,1:2])## [1] "data.frame"
class(df1[,1])## [1] "integer"
df2 <- tibble(x = 1:3, y = 3:1)
class(df2[, 1:2])## [1] "tbl_df" "tbl" "data.frame"
class(df2[, 1])## [1] "tbl_df" "tbl" "data.frame"
# Se quiser extrair só uma coluna, utilize '[[' ou '$'
class(df2[[1]])## [1] "integer"
class(df2$x)## [1] "integer"
Elas também não aceitam ‘partial matching’ de nomes de variáveis.
df <- data.frame(nome_de_cavalo = 1)
df$nome## [1] 1
df2 <- tibble(nome_de_cavalo = 1)
df2$nome## Warning: Unknown or uninitialised column: `nome`.
## NULL
Ok, nossos dados estão no R, mas, muitas vezes, não estão no formato adequado. De maneira geral, analistas de dados vão dar preferência a um formato parecido com este:
include_graphics("tidy-1.png")
Isto tem uma razão de ser que deve tornar-se óbvia quando tentarmos realizar as operações de transformação de variáveis, visualização, modelos, etc. Porém, muitas vezes outras considerações são feitas na hora registrar os dados, armazená-los, apresentá-los ao público, por isso, frequentemente nossos dados não estão no formato tidy e precisam ser reformatados. Essa é uma das principais tarefas do tidyr e é nela que vamos nos concentrar.
Atente que reformatação, como muitos outros aspectos da análise de dados, não é receita de bolo. Muitas vezes o formato desejado não é óbvio, muito menos os passos necessários para chegar lá. Porém, vou apresentar as ferramentas e alguns exemplos simples que cobrem muitos dos nossos casos de uso.
library(tidyr)
whoEsse é um banco de dados difícil de analisar, ele tem 60 colunas, indicando o número de casos de tuberculose em diversos estágios da doença, por país e ano. O problema é que ao invés de termos algo como:
tribble(
~pais, ~ano, ~tipo, ~idade, ~casos,
"brasil", 1980, "extrapulmonar", "15-24", 10,
"brasil", 1990, "relapso", "15-24", 10
)As informações de tipo de tuberculose e idade dos pacientes estão espalhadas pelas colunas. Pra encurtar a história, precisamos “tombar” esse banco para que essas colunas se tornem um novo conjunto de variáveis. Vamos passo a passo.
# Primeiro, vamos excluir as colunas iso2 e iso3, porque elas são a mesma informação redundante
who$iso2 <- NULL
who$iso3 <- NULL
who1 <- pivot_longer(who,
cols = c(new_sp_m014:newrel_f65),
names_to = "tipo_tb",
values_to = "casos",
values_drop_na = TRUE)
who1Nosso primeiro passo é transformar todas as colunas de novos casos em um par de colunas:
cols indica quais colunas serão tombadas e quais serão mantidas.names_to recebe as categorias da variável.values_to recebe os valores das células.values_drop_na é uma opção para eliminar células vazias.Essa primeira transformação já nos dá um banco de dados um pouco mais amigável, porém, ainda temos variáveis “presas” na coluna tipo_tb. Vamos tentar soltá-las.
# primeiro, corrigir uma pequena inconsistencia:
unique(who1$tipo_tb)## [1] "new_sp_m014" "new_sp_m1524" "new_sp_m2534" "new_sp_m3544" "new_sp_m4554"
## [6] "new_sp_m5564" "new_sp_m65" "new_sp_f014" "new_sp_f1524" "new_sp_f2534"
## [11] "new_sp_f3544" "new_sp_f4554" "new_sp_f5564" "new_sp_f65" "new_sn_m014"
## [16] "new_sn_m1524" "new_sn_m2534" "new_sn_m3544" "new_sn_m4554" "new_sn_m5564"
## [21] "new_sn_m65" "new_ep_m014" "new_ep_m1524" "new_ep_m2534" "new_ep_m3544"
## [26] "new_ep_m4554" "new_ep_m5564" "new_ep_m65" "new_sn_f014" "newrel_m014"
## [31] "newrel_f014" "new_sn_f1524" "new_sn_f2534" "new_sn_f3544" "new_sn_f4554"
## [36] "new_sn_f5564" "new_sn_f65" "new_ep_f014" "new_ep_f1524" "new_ep_f2534"
## [41] "new_ep_f3544" "new_ep_f4554" "new_ep_f5564" "new_ep_f65" "newrel_m1524"
## [46] "newrel_m2534" "newrel_m3544" "newrel_m4554" "newrel_m5564" "newrel_m65"
## [51] "newrel_f1524" "newrel_f2534" "newrel_f3544" "newrel_f4554" "newrel_f5564"
## [56] "newrel_f65"
# Notem que newrel deveria ser new_rel
# Alguns de vocês devem conhecer 'gsub'
who1$tipo_tb <- gsub("newrel", "new_rel", who1$tipo_tb)
# Agora, podemos usar outra função chave do tidyr, 'separate'
who2 <- who1 %>% separate(col = tipo_tb,
into = c(NA, "tipo_tb", "sexo_idade"),
sep = "_")
who2# E outra passagem de separate para separar a idade do sexo
who3 <- who2 %>% separate(col = sexo_idade,
into = c("sexo", "idade"),
sep = 1)
who3Bem melhor, não acham? Estamos agora com um banco de dados muito mais adequado para uma análise de dados em R. Cada linha é uma observação, cada coluna é uma informação sobre ela.
Alguns de vocês podem ter reparado que fizemos um caminho em que nosso banco de dados passou de ter muitas colunas para muitas linhas (ficou mais “longo”) e depois precisamos separar algumas das colunas que criamos em outras (o que fizemos com separate). Podemos facilmente imaginar situações em que queremos fazer o caminho inverso: transformar um banco do formato longo para o formato com mais colunas e unir colunas separadas em uma nova. Vamos ver um exemplo.
# Exemplo adaptado de https://en.wikipedia.org/wiki/List_of_countries_and_dependencies_by_population
populacao <- tribble(
~Rank, ~Country, ~Population, ~'% of world', ~Day, ~Month, ~Year, ~Source,
1L, "China", 1411778724, "17.9%", "1", "Nov", "2020", "Seventh Census on 2020",
2L, "India", 1377123716, "17.5%", "19", "May", "2021", "National population clock[3]",
3L, "United States", 331695937, "4.22%", "19", "May", "2021", "National population clock[4]",
4L, "Indonesia", 271350000, "3.45%", "31", "Dec", "2020", "National annual estimate[5]",
5L, "Pakistan", 225200000, "2.86%", "1", "Jul", "2021", "UN projection[2]",
6L, "Brazil", 213154869, "2.71%", "19", "May", "2021", "National population clock[6]",
7L, "Nigeria", 211401000, "2.69%", "1", "Jul", "2021", "UN projection[2]",
8L, "Bangladesh", 170689832, "2.17%", "19", "May", "2021", "National population clock[7]",
9L, "Russia", 146171015, "1.86%", "1", "Jan", "2021", "National annual estimate[8]",
10L, "Mexico", 126014024, "1.60%", "2", "Mar", "2020", "2020 census result[9]"
)
populacaoImagine que, por qualquer motivo, você prefira trabalhar com uma variável “Data” ao invés de dia, mês e ano. Podemos usar unite:
populacao2 <- populacao %>% unite(col = Data, Day, Month, Year, sep = " ")
populacao2O outro problema que precisamos resolver frequentemente, é separar um par de variáveis em diversas colunas, fazendo o caminho inverso que fizemos no caso do dataset da OMS.
us_rent_incomeNo exemplo acima, queremos separar em colunas os valores das variáveis de renda e valor do aluguel. Esse tipo de operação tem um certo grau de abstração que me deu bastante dor de cabeça para entender inicialmente, então vamos olhar com carinho para o que queremos ter depois da transformação.
us_rent_income2 <- tribble(
~GEOID, ~NAME, ~income_estimate, ~rent_estimate, ~income_moe, ~rent_moe,
"01", "Alabama", 24476, 747, 136, 3,
"02", "Alaska", 32940, 1200, 508, 13
)
us_rent_income2O banco que queremos tem uma cara assim. Ele tem mais colunas novas e menos linhas, já que eu tinha no formato tidy uma variável chamada “variable” que guardava os valores renda e aluguel e duas colunas que guardavam os valores da estimativa e do moe. Agora, eu vou ter 4 colunas, duas para as estimativas de renda e aluguel e duas para os moes das mesmas variáveis. Como especificar isso para o banco todo? Usando pivot_wider.
us_rent_income %>% pivot_wider(names_from = variable, values_from = c(estimate, moe))As funções pivot_ tem diversos outros argumentos e cobrem diversos casos de uso. Vejam este exemplo da documentação de pivot_longer:
anscombePodemos transformar esse banco de dados rapidamente usando um dos argumentos de pivot_longer, chamado names_pattern.
anscombe %>% pivot_longer(everything(),
names_to = c(".value", "set"),
names_pattern = "(.)(.)")Esse exemplo é interessante, porque ele se aproveita de uma “regular expression”, tema da parte do nosso curso em que falaremos sobre manipulação de strings com o stringr. Resumidas as contas, as colunas se chamam “x1, x2, x3 …” e a string “(.)(.)” indica que há dois “grupos” formados por um caractere cada. A string “.value” que vai no argumento de cima é um atalho da função para dizer “pegue o valor de todas as células das variáveis selecionadas”, aqui, todas. Ou seja, ele indica para a função que o primeiro caractere “x” ou “y” definirá uma nova variável e armazenará os valores das celulas, enquanto o segundo grupo “1”, “2”, “3” ou “4” formará uma segunda variável chamada “set” que contém apenas os nomes das colunas. Deu um nó na cabeça?
Uma última preocupação ao utilizar a reformatação de dados é o que ocorre com os valores NA. Vejamos este exemplo:
acoes <- tibble(
ano = c(2015, 2015, 2015, 2015, 2016, 2016, 2016),
qdr = c( 1, 2, 3, 4, 2, 3, 4),
lucro = c(1.88, 0.59, 0.35, NA, 0.92, 0.17, 2.66)
)Existem dois tipos de valor nulo, explícito se diz de um valor nulo como aquele NA que aparece na variável lucro. Implícito é o valor que ocorre no primeiro quadrimestre de 2016, onde sequer foi adicionada uma linha no banco de dados. Os valores implícitos são muito sacanas, porque eles não são imediatamente visíveis.
acoes %>%
pivot_wider(names_from = ano, values_from = lucro)Ao transformar o banco, o valor implícito ficou explícito. Caso você não esteja interessado neste valor, você pode passar o values_drop_na durante a transformação de volta ao formato original.
acoes %>%
pivot_wider(names_from = ano, values_from = lucro) %>%
pivot_longer(c(`2015`, `2016`),
names_to = "ano",
values_to = "lucro",
values_drop_na = TRUE)Que faz os valores missing desaparecem.
complete pode ser usada pra tornar valores implícitos, explícitos! A função toma todas as colunas pedidas e verifica todas as combinações possíveis de valores, preenchendo as lacunas com NA. Cuidado ao utilizar complete com valores numéricos ou bancos de dados muito grandes, pois o número de combinações pode ser infinitamente grande e travar sua sessão.
acoes %>% complete(ano, qdr)Pra encerrar, fill serve para aqueles casos em que um valor missing indica que a última observação deve ser repetida. Pesquisadores brasileiros das antigas podem lembrar-se do Censo de 1991, em que o IBGE registrava os arquivos de domícilio e pessoas com esse sistema. Em inglês, isso se chama LOCF, ou “last observation carried forward”.
treatment <- tribble(
~ person, ~ treatment, ~response,
"Derrick Whitmore", 1, 7,
NA, 2, 10,
NA, 3, 9,
"Katherine Burke", 1, 4
)
treatmenttreatment %>% fill(person)Tidyr tem também outras funcionalidades relevantes para modelagem estatística, mas acho que isso sai um pouco do escopo do curso. Quem sabe a gente não faz um curso posterior só sobre modelagem no tidyverse?
O pacote readr apresenta uma família de funções para substituir as funções do base relacionadas a importação de arquivos em formato texto, seja delimitado ou largura-fixa. São elas,
read_delimread_csvread_csv2read_tsvread_tableread_fwfE assim sucessivamente. Durante o processo de importação, você pode querer especificar o tipo de coluna com cols ou cols_only, usando o argumento col_types. Ou use uma string do tipo “ddcdiDT” em que cada letra é um tipo de variável.
col_integercol_doublecol_factorcol_characterEtc. Você também pode querer definir características de localização, como a codificação de caracteres, os separadores de decimal e de milhar e etc. A melhor forma de fazer isso é definir um locale.
Ah, e você sempre pode salvar com write_, inclusive salvando/lendo compactado para bzip, gzip ou xzip.
tibbleTibbles são uma versão do data.frame com algumas regrinhas novas. Vou apenas repetí-las aqui de forma resumida.
[ e $.tidyrTidyr é um pacote de reformatação de bancos, criando novas linhas e colunas a partir da reorganização das variáveis e valores existentes. Suas principais operações são:
pivot_longer para converter colunas em linhaspivot_wider para converter linhas em colunasseparate para separar uma coluna em várias com base em caracteresunite para unir diversas colunas em uma com base em caracteresUfa. Acabou né? Posso ir dormir já? Claro, só fazer uns exercícios!
file <- readr_example("epa78.txt")file <- readr_example("challenge.csv")sala_aula <- tribble(
~name, ~teste1, ~teste2, ~prova1,
"Billy", "<NA>", "D" , "C",
"Suzy", "F", "<NA>", "<NA>",
"Lionel", "B", "C" , "B",
"Jenny", "A", "A" , "B"
)relig_income para que ele contenha as colunas religião, renda e frequência.relig_incomebillboard para que ele contenha apenas uma coluna “semana” e uma coluna com a posição da música no ranking.# Dica, você pode selecionar várias colunas usando o atalho wk1:wk76
billboard Experimente fazer o caminho inverso dos exercícios 3 a 5, devolvendo os datasets ao seu formato original. O que você observou?
O que os argumentos extra e fill em separate fazem? Utilize o exemplo a seguir como guia.
tibble(x = c("a,b,c", "d,e,f,g", "h,i,j")) %>%
separate(x, c("um", "dois", "tres"))## Warning: Expected 3 pieces. Additional pieces discarded in 1 rows [2].
tibble(x = c("a,b,c", "d,e", "f,g,i")) %>%
separate(x, c("um", "dois", "tres"))## Warning: Expected 3 pieces. Missing pieces filled with `NA` in 1 rows [2].
Tanto unite como separate possuem um argumento remove. Pra que ele serve e quando você o utilizaria no valor FALSE?
Compare o argumento values_fill em pivot_wider e fill em complete. Qual é a diferença?
Esse material é uma adaptação livre das vinhetas dos pacotes tidyr, readr e tibble e do capítulo Tidy Data do R for Data Science, de Wickham & Grolemund.
Copyright © 2020 Vinícius Maia. Nenhum Direito a menos.